<template>
    <div class="upload-ui" v-bind:class="{ 'need-hide': needHide }">
        <input class="upload-ui-input" type="file" :accept="input.accepts" @change="_input" :multiple="multiple" name="upload-ui-input" ref="upload-ui-input">
        <div style="display: inline-block; height: 100%;width: 100%;" @dragenter="_dragenter" @dragend="_dragend" @dragover="_dragover" @drop="_drop" @click="select">
            <slot>
                <el-button :size="btnSize" type="primary">选择文件</el-button>
            </slot>
        </div>
        <!-- 裁剪图片 -->
        <crop-pic ref="CropPicUI" :cropInfo="cropInfo"></crop-pic>
    </div>
</template>	

<script>
/** 
 * @Author hedong [2022-02-28 17:25:18]
 * @Description 上传组件
 */
import FILE from '@utils/file.js';
import CropPic from './crop-pic';
import { UploadCfg } from "./index";

import $http from '@http';

import { mapState, mapActions, mapMutations } from 'vuex';

export default {
    name: 'upload-ui',

    components: {
        [CropPic.name]: CropPic
    },

    computed: {
        ...mapState({
            upload: state => state.upload
        })
    },

    props: {
        // 配置数据（当不传该参数时，将会使用通用配置[ @ui/index.js中所配置的通用上传参数 ]）
        props: { type: UploadCfg, default: undefined },
        // 是否开启自动上传
        auto: { type: Boolean, default: false },
        // 是否支持多文件上传
        multiple: { type: Boolean, default: false },
        // 限制文件大小 (M) 
        limitCount: { type: Number, default: undefined },
        // 是否允许拖拽上传
        drag: { type: Boolean, default: false },
        // 是否分片上传
        slice: { type: Boolean, default: true },
        // 默认分片大小 (M)
        sliceSize: { type: Number, default: 2 },
        // 上传的默认请求方式
        method: {
            type: String, default: 'POST', validator(val) {
                return ['GET', 'PUT', 'POST'].includes(val);
            }
        },
        headers: { type: Object, default() { return {} } },
        // 文件参数的key
        fileKey: { type: String, default: 'file' },
        // 上传的参数
        params: { type: Object, default() { return {} } },
        // 上传类型// 如果 server 有数据则使用 server 作为上传地址, 否则使用类型进行判断 
        action: {
            type: String, default: undefined, validator(value) {
                return ['material', 'pic', 'cover', 'other'].includes(value);
            }
        },
        // 上传地址
        server: { type: String, default: undefined },
        // 指定接受哪些类型的文件 ['image', 'video', 'audio', 'word', 'all']
        acceptTypes: { type: Array, default() { return ['all'] } },
        // 指定接受哪些类型的文件 ['.jpg', '.jpeg', '.png', '.excel']
        accepts: { type: Array, default() { return [] } },
        // 是否显示上传列表
        showList: { type: Boolean, default: false },
        // 设置上传状态
        uploadStatus: { type: String, validator(val) { return ['start', 'stop', 'cancel', 'waiting'].includes(val); } },
        // 限制文件大小 (M)
        limitSize: { type: Number, default: undefined },
        // 文件上传前得回调函数 item：文件对象
        fileBeforeUpload: { type: Function, default: undefined },
        // 单片文件上传前的回调函数
        pieceBeforeUpload: { type: Function, default: undefined },
        // 取消某个正在上传的文件 file：文件对象
        cancelFile: { type: Function, default: undefined },
        //上传前过滤文件类型
        onFileFilter: { type: Function, default: undefined },
        // 是否需要隐藏组件
        needHide: { type: Boolean, default: false },
        // 禁止上传
        disabled: { type: Boolean, default: false },
        //是否裁剪图片
        isCrop: { type: Boolean, default: false },
        // 裁剪信息
        cropInfo: {
            type: Object, default() {
                return {
                    autoCropWidth: 200,//裁剪框的宽度
                    autoCropHeight: 200,//裁剪框的高度
                    enlarge: 1,// 裁剪出来的图片是裁剪框的倍数
                }
            }
        },
        // 限制文件名的长度
        limitLength: { type: Number, default: undefined },
        // 截取文件名的长度
        interceptLength: { type: Number, default: undefined },
        btnSize: { type: String, default: 'small' },//按钮大小
    },

    watch: {
        uploadStatus(val) {
            if (this.status !== 'uploading' && val === 'start') {
                this._continue();
            } else if (val === 'stop') {
                this.status = 'stoped';
            }
        },
        'url.test'() {
            this._continue();
        },
        'url.upload'() {
            this._continue();
        },
        uploadings() {
            this._onListChange()
        },
        waitings() {
            this._onListChange()
        },
        succeeds() {
            this._onListChange()
        },
        faileds() {
            this._onListChange()
        },
        // 监听限制的类型变化
        acceptTypes: {
            handler() {
                this.input.accepts = FILE.accept(this.acceptTypes);
            }, immediate: true, deep: true
        }
    },

    data() {
        return {
            url: {
                // 上传路径
                upload: undefined,
                // 检测续传记录路径
                test: undefined
            },
            custom: {
                drop: false,
                input: false
            },
            input: {
                // 可上传文件类型
                accepts: [],
            },
            limit: {
                size: undefined,
                count: 0
            },
            piece: 0,
            // 待上传文件列表
            waitings: [],
            // 正在上传文件列表
            uploadings: [],
            // 上传成功文件列表
            succeeds: [],
            // 上传失败文件列表
            faileds: [],
            // 已暂停上传的文件
            pauseds: [],
            // 上传状态 uploading stoped
            status: 'waiting',
            stop: false,
            // 记录整个上传暂停时的数据
            stopedObj: {
                // 暂停类型 chunk：分片, next：下一个文件
                type: 'next',
                // 分片暂停开始
                start: 0,
                // 分片暂停的file
                file: null,
                // 分片暂停的回调
                callback: null
            },
        }
    },

    created() {
        // this._2SetupCfg(this.props);
        // console.log("创建组件：");
    },

    mounted() {
        this.$emit('on-mounted');
        if (!this.action) {
            console.error('[upload-ui] 请设置上传文件类型 (action)');
            return;
        }

        this.url.test = `/file/temp`;

        if (this.server) {
            this.url.upload = this.server;
        } else {
            this.url.upload = `/file/${this.action}/upload`;
        }

        this.piece = (this.slice) ? this.sliceSize * 1024 * 1024 : Number.MAX_VALUE;
        this.limit.count = (this.multiple) ? this.limitCount : 1;

        // 初始化可上传文件类型
        if (this.accepts && this.accepts.length) {
            this.input.accepts = this.accepts.concat([]);
        } else {
            this.input.accepts = FILE.accept(this.acceptTypes);
        }
        this.custom.drop = this.$slots.drop ? true : false;
        this.custom.input = this.$slots.default ? true : false;
    },

    methods: {
        /**
         * Store Action 方法
         */
        ...mapActions({
            _2SelectFile: 'upload-store/select',
        }),

        /**
         * Store Mutation 方法
         */
        ...mapMutations({
            //_2SetupCfg: 'upload-store/setup'
        }),

        _dragenter(event) {
            event.preventDefault();
        },

        _dragend(event) {
            event.preventDefault();
        },

        _dragover(event) {
            event.preventDefault();
        },

        _drop(event) {
            event.preventDefault();
            if (!this.drag) return;
            const files = event.dataTransfer.files;
            this._toAddFiles(files);
        },

        // 监听input change 事件
        _input(event) {
            // 限制每次只能上传一个文件
            // if (this.upload.list.length != 0) {
            //     return this.$notify.warning("上传任务已存在")
            // }

            //单文件上传，裁剪图片
            if (!this.multiple && this.isCrop) {
                var file = event.target.files[0];
                if (!/\.(gif|jpg|jpeg|png|icon|bmp|GIF|JPG|PNG|ICON)$/.test(event.target.value)) {
                    alert("图片类型必须是.gif,jpeg,jpg,png,bmp,icon中的一种");
                    return false;
                }
                var reader = new FileReader();
                reader.onload = e => {
                    let data = e.target.result;
                    this._onCropImage(data, file.name)
                };
                // 转化为base64
                reader.readAsDataURL(file)
                // 转化为blob
                // reader.readAsArrayBuffer(file);
            } else {
                let elem = event.currentTarget;
                const files = [...elem.files];
                this._toAddFiles(files);
                elem.value = '';
            }
        },
        _onListChange() {
            this.$emit('list-change', this.uploadings.concat(this.waitings).concat(this.succeeds).concat(this.faileds));
        },
        // 添加文件到队列
        _toAddFiles(files) {
            if (this.status == 'uploading' && this.multiple == false) return
            files = this._validator(files);
            if (this.onFileFilter) {
                files = this.onFileFilter(files)
            }
            if (files == undefined || files.length == 0) {
                return
            }
            let samples = [];
            for (let i = 0; i < files.length; i++) {
                let file = files[i];
                samples.push(this._toAddFile(file, this.params));
            }
            this._onAddFileEnd(samples);
        },
        //裁剪图片
        _onCropImage(url, fileName) {
            this.$refs.CropPicUI.show(url, { name: fileName }).then(data => {
                this._toAddFiles([data]);
            }).catch(() => { })
        },

        /**
         * 添加单个文件
         */
        _toAddFile(file, params) {
            const { name, lastModified: lastModify, size: fileSize } = file;
            let fileName = name
            // 截取文件名的长度
            if (this.interceptLength) {
                fileName = name.substr(0, this.interceptLength)
            }
            const fileType = FILE.type(file);
            const id = `${fileName}-${fileSize}-${lastModify}-${Date.now()}`;
            const form = Object.assign({ fileType, fileName, fileSize, lastModify }, params);
            const status = 'waiting';
            const uploaded = 0, progress = 0;
            const info = {
                file, id, fileName, fileSize, lastModify, status, uploaded, progress, form
            };
            this.$emit('on-add', info);
            return info;
        },

        /**
         * 添加文件完成(准备上传)
         */
        _onAddFileEnd(infos) {
            this.waitings = this.waitings.concat(infos);
            const LIMIT = (this.multiple) ? (this.limitCount) ? this.limitCount : Number.MAX_VALUE : 1;
            while (this.waitings.length > LIMIT) {
                this.waitings.pop();
            }
            this._continue();
        },

        // 继续上传
        _continue() {
            if (this.status === 'waiting' && this.auto === true) {
                this.status = 'uploading';
                this._tryToUpload();
            } else if (this.status == 'uploading') {
                this._tryToUpload();
            }
        },

        // 尝试上传
        _tryToUpload() {

            if (!this.url.test || !this.url.upload) {
                this.status = 'waiting';
                return;
            }

            if (this.status !== 'uploading') {
                this.stopedObj.type = 'next';
                return;
            }
            let item = this.waitings.shift();
            if (item) {
                localStorage.setItem('uploading', 'true');
                let { form, file: { size } } = item;
                Object.assign(form, { businessEnum: this.action })
                $http.get(this.url.test, form).then(uploaded => {
                    item.status = 'uploading';
                    item.uploaded = uploaded || 0;
                    item.progress = uploaded / size;
                    // 将信息存放到上传中列表
                    this.uploadings.push(item);
                    this._toUpload(item, uploaded, this._onUploaded);
                }).catch(() => {
                    this._onUploaded('error', item);
                });
            } else {
                // 全部文件已上传完成
                this.status = 'waiting';
                localStorage.setItem('uploading', 'false');
                this.$emit('on-complete');
            }
        },
        clearInput() {
            this.$refs['upload-ui-input'].value = null

        },
        // 上传文件
        async _toUpload(item, uploaded, complition) {
            // 判断文件上传状态
            if (item.status !== 'uploading') {
                complition && complition('stoped', item);
                return;
            }
            let { file } = item;
            let { size } = file;
            let start = uploaded;
            let end = Math.min(uploaded + this.piece, size);
            let target = file.slice(start, end);
            let data = Object.assign({}, item.form, {
                startSize: start,
                [this.fileKey]: target
            }, this.params);

            const form = new FormData();
            Object.keys(data).forEach(key => {
                form.append(key, data[key]);
            });

            try {
                if (item.status !== 'uploading') {
                    complition && complition('stoped', item);
                    return;
                }
                // 请求接口
                let result = undefined;
                if (this.method == 'POST') {
                    result = await $http.post(this.url.upload, form, { 'Content-Type': 'multipart/form-data' });
                } else if (this.method == 'PUT') {
                    result = await $http.put(this.url.upload, form, { 'Content-Type': 'multipart/form-data' });
                } else {
                    result = await $http.get(this.url.upload, form, { 'Content-Type': 'multipart/form-data' });
                }
                let { startSize, fileUUID } = result;
                item.uploaded = startSize;
                item.progress = startSize / size;
                item.fileUUID = fileUUID;
                this.$emit('on-progress', item);
                if (item.progress >= 1) {
                    this.$emit('on-success', item, result);
                    complition('success', item);
                    return;
                }
                console.log(item.status);
                if (item.status == 'cancel') {
                    let callback = item.callback;
                    delete item.callback;
                    $http.delete(`file/temp`, item.form).then(() => {
                        this._onUploaded('stoped', item, !this.stop);
                        this.stop = false;
                        callback && callback(true);
                    }).catch(() => {
                        callback && callback(false);
                    })
                    return;
                }

                this._toUpload(item, startSize, complition);
            } catch (error) {
                // 服务器错误
                this.$emit('on-error', item, error);
                complition('error', item);
            }
        },

        // 单文件上传完成 不管成功还是失败
        _onUploaded(flag, item, goon = true) {
            this.uploadings = this.uploadings.filter(f => {
                return f.id !== item.id;
            });
            if (flag === 'success') {
                item.status = 'success';
                this.succeeds.push(item);
            } else if (flag === 'error') {
                item.status = 'error';
                this.faileds.push(item);
            } else if (flag === 'stoped') {
                item.status = 'stop';
                this.pauseds.push(item);
            }
            if (!goon) {
                this.status = 'stop';
                return;
            }
            this.status = 'uploading';
            this._tryToUpload();
        },

        // 文件类型验证// 文件名验证
        _validator(files) {
            if (!this.input.accepts.length) return files;
            let samples = [], errors = [];
            let reg = new RegExp(`[:|?*<>"]`);
            for (let i = 0; i < files.length; i++) {
                let file = files[i];
                if (!FILE.isAccept(file, this.input.accepts)) {
                    this.$notify.error('文件类型不符合要求');
                    errors.push(file.name);
                    continue;
                }
                if (reg.exec(file.name)) {
                    this.$notify.error('文件名包含了特殊字符');
                    errors.push(file.name);
                    continue;
                }
                if (file.name.length > this.limitLength) {
                    this.$notify.error(`文件名不能超过${this.limitLength}个`);
                    errors.push(file.name);
                    continue;
                }
                //限制文件名的长度
                if (this.limitLength & file.name.length > this.limitLength) {
                    this.$notify.error(`文件名不能超过${this.limitLength}个字符`);
                    errors.push(file.name);
                    continue;
                }
                //限制文件名的大小
                if (file.size <= 0 || (this.limitSize && file.size > this.limitSize * 1024 * 1024)) {
                    this.$notify.error('文件过大或空文件');
                    errors.push(file.name);
                    continue;
                }
                samples.push(file);
            }
            return samples;
        },

        /**
         * 取消上传
         */
        _cancel(item, callback) {
            this.status = 'stop';
            item.status = 'cancel';
            item.callback = callback;
            // $http.delete(`/file/${this.action}/temp`, item.form).then(() => {
            $http.delete(`file/temp`, item.form).then(() => {
                if (this.stop) {
                    item.status = 'cancel';
                    this.$emit('on-cancel', item);
                    callback && callback(true);
                    return;
                }
                this.status = 'uploading';
                this._onUploaded('stoped', item);

                callback && callback(true);
            }).catch(() => {
                callback && callback(false);
            })
        },

        // 取消某个文件
        cancel(id, callback, stop = false) {
            this.stop = stop;
            // 正在上传的文件
            let item = undefined;
            item = this.uploadings.find(f => {
                return f.id === id;
            });

            if (item) {
                // 文件正在上传, 取消上传
                this.status = 'stop';
                item.status = 'cancel';
                item.callback = callback;
                return;
            }
            // 是不是在等待列表的文件
            item = this.waitings.find(f => {
                return f.id === id;
            });

            if (item) {
                // 文件在等待列表中, 将文件从等待列表中移除
                this.waitings = this.waitings.filter(f => {
                    return f.id !== item.id;
                });
                callback && callback(true);
                return;
            }
            // 判断是不是在成功列表
            item = this.faileds.find(f => {
                return f.id === id;
            });
            if (item) {
                // 文件在错误列表中, 将文件从等待列表中移除
                this.faileds = this.faileds.filter(f => {
                    return f.id !== item.id;
                });
                callback && callback(true);
                return
            }
            // 判断是不是在成功列表
            item = this.succeeds.find(f => {
                return f.id === id;
            });

            if (item) {
                // 文件在成功列表中// 将文件从成功列表中移除
                this.succeeds = this.succeeds.filter(f => {
                    return f.id !== item.id;
                })
                callback && callback(true);
                return;
            }

            callback && callback(true);
        },

        // 开始上传文件
        submit(files = []) {
            console.log(files);
            if (files && (files instanceof Array)) {
                files = files.filter(file => {
                    return (file instanceof File) || (file instanceof Blob)
                });
                if (files.length) {
                    this._toAddFiles(files);
                }
                if (!this.waitings.length) {
                    this.$emit('on-complete');
                    return;
                }
            }
            if (this.status !== 'uploading') {
                this.status = 'uploading';
                this._continue();
            }
        },

        /**
         * 添加待上传文件
         */
        add(file, params) {
            const info = this._toAddFile(file, params);
            this._onAddFileEnd([info]);
        },

        /**
         * 选择文件
         */
        select() {
            /**
             * ==========
             * 2012-03-01 START
             */
            // this._2SelectFile(this.props);
            /**
             * ==========
             * 2012-03-01 END
             */

            // /*
            // 原
            if (this.status == 'uploading' && this.multiple == false) return;
            if (this.disabled) {
                this.$notify.warning('禁止上传');
                return;
            }
            if (this.isChange) {
                this.$confirm('更改文件会直接替换原文件', '提示', {
                    confirmButtonText: '确 定',
                    cancelButtonText: '取 消',
                    confirmButtonLoading: false,
                    closeOnClickModal: false,
                    type: 'warning',
                    beforeClose: (action, instance, done) => {
                        if (action == 'cancel') return done();
                        this.$refs['upload-ui-input'].click();
                        this.$confirm.submiting(instance, false, '确 定')
                        done();
                    }
                }).then(() => { }).catch(() => { })
            } else {
                this.$refs['upload-ui-input'].value = null
                this.$refs['upload-ui-input'].click();
            }
            // */
        }
    },
    destroyed() {
    },
}
</script>

<style lang="scss" scoped>
// 默认上传按钮
.upload-ui {
    display: inline-block;
    position: relative;
    overflow: hidden;
    vertical-align: middle;
    .upload-ui-input {
        display: none;
        position: absolute;
        width: 0;
        height: 0;
    }
    .upload-ui-content {
        padding: 5px 12px;
        font-size: 0.9rem;
        border-radius: 3px;
        cursor: default;
        color: white;
        &:hover {
            color: red;
        }
    }

    &.need-hide {
        position: absolute;
        left: -1000px;
        top: -1000px;
    }
}

// 上传框体
.upload-ui-box {
    $headerHeight: 43px;
    $bottom: 320px;
    position: fixed;
    z-index: 999;
    bottom: -$bottom;
    right: 32px;
    width: 560px;
    height: $bottom;
    border-top-left-radius: 7px;
    border-top-right-radius: 7px;
    border: 1px solid #e2e2e2;
    box-shadow: 0 0 10px #ccc;
    margin-bottom: -2px;
    background-color: #fff;
    transition: bottom 0.5s;
    &.finished {
        bottom: $headerHeight - $bottom;
        .upload-ui-tool.oc-button::before {
            content: '展开';
        }
    }
    &.active {
        bottom: 0;
    }
    .upload-ui-header {
        position: relative;
        font-size: 0.9rem;
        border-top-left-radius: 7px;
        border-top-right-radius: 7px;
        background-color: #fff;
        height: $headerHeight;
        line-height: $headerHeight;
        padding: 0 10px;
        border-bottom: 1px solid #f7f3f3;
        color: #9e9e9e;
        .upload-ui-tools {
            position: absolute;
            right: 10px;
            top: 0;
            & .upload-ui-tool {
                cursor: pointer;
                &:not(:last-child) {
                    margin-right: 5px;
                }
                &:hover {
                    color: #409eff;
                }
            }
        }
    }
    .upload-ui-tool.oc-button::before {
        content: '收起';
    }
    // 文件列表
    .upload-ui-content {
        font-size: 0.9rem;
        background-color: #fff;
        .upload-ui-list-header {
            overflow: hidden;
        }
        .upload-ui-list-body {
            margin: 0;
            width: 100%;
            height: 14em;
            overflow: auto;
            padding: 0;
        }
        .upload-ui-file-item {
            overflow: hidden;
            width: 100%;
        }
        .file-s {
            float: left;
            height: 35px;
            line-height: 35px;
            color: #424e67;
        }
        .upload-ui-file-name {
            width: 45%;
            padding-left: 10px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            text-align: left;
        }
        .upload-ui-file-size {
            width: 20%;
        }
        .upload-ui-file-path {
            width: 13%;
        }
        .upload-ui-file-status {
            width: 20%;
        }
        .upload-ui-file-operate {
            width: 15%;
        }
        .upload-ui-remove-button {
            font-size: 0.8rem;
            padding: 2px 3px;
            background-color: #f44336;
            color: #fff;
            border-radius: 3px;
            cursor: pointer;
            &:hover {
                background-color: #d64d43;
            }
        }
    }
}
</style>